#include "JsonMap_rapidjson.h"

namespace
{
#define PUT_FUNCTION(key, value, putListBehavior) d->putPreprocess(key, putListBehavior); \
    for (const auto &v: value) {d->document[key].append(Json::Value(v)); }
#define CHECK_PARSER if(!d->isParserOk){throw sh::JsonMapFormatException(d->error);}
}

class JsonMapData
{
private:
    friend class sh::JsonMap;
    JsonMapData();
    bool isParserOk{false};
    rapidjson::Document document;
    std::string error;

    static std::string getErrorMessage(rapidjson::ParseErrorCode errorCode);
};
JsonMapData::JsonMapData()
{
    document.SetObject();
}
std::string JsonMapData::getErrorMessage(rapidjson::ParseErrorCode errorCode)
{
    std::string errorMessage;
    switch (errorCode)
    {
        case rapidjson::ParseErrorCode::kParseErrorNone:
            errorMessage = "No error";
            break;
        case rapidjson::ParseErrorCode::kParseErrorDocumentEmpty:
            errorMessage = "The document is empty";
            break;
        case rapidjson::ParseErrorCode::kParseErrorDocumentRootNotSingular:
            errorMessage = "The document root must not follow by other values";
            break;
        case rapidjson::ParseErrorCode::kParseErrorValueInvalid:
            errorMessage = "Invalid value";
            break;
        case rapidjson::ParseErrorCode::kParseErrorObjectMissName:
            errorMessage = "Missing a name for object member";
            break;
        case rapidjson::ParseErrorCode::kParseErrorObjectMissColon:
            errorMessage = "Missing a colon after a name of object member";
            break;
        case rapidjson::ParseErrorCode::kParseErrorObjectMissCommaOrCurlyBracket:
            errorMessage = "Missing a comma or '}' after an object member";
            break;
        case rapidjson::ParseErrorCode::kParseErrorArrayMissCommaOrSquareBracket:
            errorMessage = "Missing a comma or ']' after an array element";
            break;
        case rapidjson::ParseErrorCode::kParseErrorStringUnicodeEscapeInvalidHex:
            errorMessage = "Incorrect hex digit after \\u escape in string";
            break;
        case rapidjson::ParseErrorCode::kParseErrorStringUnicodeSurrogateInvalid:
            errorMessage = "The surrogate pair in string is invalid";
            break;
        case rapidjson::ParseErrorCode::kParseErrorStringEscapeInvalid:
            errorMessage = "Invalid escape character in string";
            break;
        case rapidjson::ParseErrorCode::kParseErrorStringMissQuotationMark:
            errorMessage = "Missing a closing quotation mark in string";
            break;
        case rapidjson::ParseErrorCode::kParseErrorStringInvalidEncoding:
            errorMessage = "Invalid encoding in string";
            break;
        case rapidjson::ParseErrorCode::kParseErrorNumberTooBig:
            errorMessage = "Number too big to be stored in double";
            break;
        case rapidjson::ParseErrorCode::kParseErrorNumberMissFraction:
            errorMessage = "Miss fraction part in number";
            break;
        case rapidjson::ParseErrorCode::kParseErrorNumberMissExponent:
            errorMessage = "Miss exponent in number";
            break;
        case rapidjson::ParseErrorCode::kParseErrorTermination:
            errorMessage = "Parsing was terminated";
            break;
        case rapidjson::ParseErrorCode::kParseErrorUnspecificSyntaxError:
            errorMessage = "Unspecific syntax error";
            break;
        default:
            errorMessage = "Unknown error";
            break;
    }
    return errorMessage;
}
sh::JsonMap::JsonMap()
    : d(std::unique_ptr<JsonMapData>(new JsonMapData()))
{
    d = std::unique_ptr<JsonMapData>(new JsonMapData());
    d->isParserOk = true;
}
sh::JsonMap::JsonMap(const std::string &content)
    : d(std::unique_ptr<JsonMapData>(new JsonMapData()))
{
    d = std::unique_ptr<JsonMapData>(new JsonMapData());
    parser(content);
}
sh::JsonMap::JsonMap(const sh::JsonMap &jsonMap)
    : d(std::unique_ptr<JsonMapData>(new JsonMapData()))
{
    d->document.CopyFrom(jsonMap.d->document, jsonMap.d->document.GetAllocator());
    d->isParserOk = true;
}
sh::JsonMap::JsonMap(const rapidjson::Value &value)
    : d(std::unique_ptr<JsonMapData>(new JsonMapData()))
{
    d->document.CopyFrom(value, d->document.GetAllocator());
    d->isParserOk = true;
}
sh::JsonMap::~JsonMap() = default;

bool sh::JsonMap::parser(const std::string &content)
{
    d->document.Parse(content.c_str());
    auto code = d->document.GetParseError();
    d->error = d->getErrorMessage(code);
    d->isParserOk = rapidjson::ParseErrorCode::kParseErrorNone == code;
    return d->isParserOk;
}
bool sh::JsonMap::isParserOk() const
{
    return !d->document.HasParseError();
}
std::string sh::JsonMap::getParserErrorMessage() const
{
    return d->error;
}
std::string sh::JsonMap::formatJson() const
{
    rapidjson::StringBuffer jsonBuffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(jsonBuffer);
    d->document.Accept(writer);
    return jsonBuffer.GetString();
}
std::string sh::JsonMap::toString() const
{
    CHECK_PARSER
    if (!d->document.IsString())
    {
        throw sh::JsonMapTypeException("type is not string");
    }
    return d->document.GetString();
}
int32_t sh::JsonMap::toInt() const
{
    CHECK_PARSER
    if (!d->document.IsInt())
    {
        throw sh::JsonMapTypeException("type is not int");
    }
    return d->document.GetInt();
}
uint32_t sh::JsonMap::toUInt() const
{
    CHECK_PARSER
    if (!d->document.IsUint())
    {
        throw sh::JsonMapTypeException("type is not unsigned int");
    }
    return d->document.GetUint();
}
int64_t sh::JsonMap::toLong() const
{
    CHECK_PARSER
    if (!d->document.IsInt64())
    {
        throw sh::JsonMapTypeException("type is not long int");
    }
    return d->document.GetInt64();
}
uint64_t sh::JsonMap::toULong() const
{
    CHECK_PARSER
    if (!d->document.IsUint64())
    {
        throw sh::JsonMapTypeException("type is not unsigned long int");
    }
    return d->document.GetUint64();
}
double sh::JsonMap::toDouble() const
{
    CHECK_PARSER
    if (!d->document.IsDouble())
    {
        throw sh::JsonMapTypeException("type is not double");
    }
    return d->document.GetDouble();
}
float sh::JsonMap::toFloat() const
{
    CHECK_PARSER
    if (!d->document.IsFloat())
    {
        throw sh::JsonMapTypeException("type is not float");
    }
    return d->document.GetFloat();
}
bool sh::JsonMap::toBool() const
{
    CHECK_PARSER
    if (!d->document.IsBool())
    {
        throw sh::JsonMapTypeException("type is not bool");
    }
    return d->document.GetBool();
}
bool sh::JsonMap::containKey(const std::string &key) const
{
    return d->document.HasMember(key.c_str());
}
sh::JsonMap sh::JsonMap::get(const std::string &key) const
{
    CHECK_PARSER
    if (!containKey(key))
    {
        throw sh::JsonMapNotExistException("key [" + key + "] is not exist");
    }
    return sh::JsonMap(d->document[key.c_str()]);
}
sh::JsonMap sh::JsonMap::remove(const std::string &key)
{
    if (d->document.HasMember(key.c_str()))
    {
        d->document.RemoveMember(key.c_str());
    }
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const sh::JsonMap &value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    d->document.AddMember(keyValue, value.d->document, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::string &value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    rapidjson::Value valueValue;
    valueValue.SetString(value.c_str(), value.length(), d->document.GetAllocator());
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, int32_t value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    rapidjson::Value valueValue;
    valueValue.SetInt(value);
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, uint32_t value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    rapidjson::Value valueValue;
    valueValue.SetUint(value);
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, int64_t value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    rapidjson::Value valueValue;
    valueValue.SetInt64(value);
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, uint64_t value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    rapidjson::Value valueValue;
    valueValue.SetUint64(value);
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, double value)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());
    rapidjson::Value valueValue;
    valueValue.SetDouble(value);
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::list<std::string> &value, sh::PutListBehavior putListBehavior)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());

    rapidjson::Value valueValue(rapidjson::kArrayType);
    rapidjson::Document::AllocatorType &allocator = d->document.GetAllocator();

    rapidjson::Value listValue(rapidjson::kStringType);
    //追加则先取出原来就存在的
    if (sh::PutListBehavior::Append == putListBehavior && d->document.HasMember(key.c_str()) && d->document[key.c_str()].IsArray())
    {
        for (auto &v: d->document[key.c_str()].GetArray())
        {
            if (!v.IsString())
            {
                //原先list中元素和目标元素不一致，则抛出异常
                throw sh::JsonMapTypeException("origin element type is not all string");
            }
            std::string originValue = v.GetString();
            listValue.SetString(originValue.c_str(), originValue.length(), allocator);
            valueValue.PushBack(listValue, allocator);
        }
    }
    //添加新元素
    for (const std::string &v: value)
    {
        listValue.SetString(v.c_str(), v.length(), allocator);
        valueValue.PushBack(listValue, allocator);
    }
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::list<int32_t> &value, sh::PutListBehavior putListBehavior)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());

    rapidjson::Value valueValue(rapidjson::kArrayType);
    rapidjson::Document::AllocatorType &allocator = d->document.GetAllocator();

    rapidjson::Value listValue(rapidjson::kNumberType);
    //追加则先取出原来就存在的
    if (sh::PutListBehavior::Append == putListBehavior && d->document.HasMember(key.c_str()) && d->document[key.c_str()].IsArray())
    {
        for (auto &v: d->document[key.c_str()].GetArray())
        {
            if (!v.IsInt())
            {
                //原先list中元素和目标元素不一致，则抛出异常
                throw sh::JsonMapTypeException("origin element type is not all int32_t");
            }
            listValue.SetInt(v.GetInt());
            valueValue.PushBack(listValue, allocator);
        }
    }
    //添加新元素
    for (const int32_t &v: value)
    {
        listValue.SetInt(v);
        valueValue.PushBack(listValue, allocator);
    }
    d->document.RemoveMember(key.c_str());
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::list<uint32_t> &value, sh::PutListBehavior putListBehavior)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());

    rapidjson::Value valueValue(rapidjson::kArrayType);
    rapidjson::Document::AllocatorType &allocator = d->document.GetAllocator();

    rapidjson::Value listValue(rapidjson::kNumberType);
    //追加则先取出原来就存在的
    if (sh::PutListBehavior::Append == putListBehavior && d->document.HasMember(key.c_str()) && d->document[key.c_str()].IsArray())
    {
        for (auto &v: d->document[key.c_str()].GetArray())
        {
            if (!v.IsUint())
            {
                //原先list中元素和目标元素不一致，则抛出异常
                throw sh::JsonMapTypeException("origin element type is not all uint32_t");
            }
            listValue.SetUint(v.GetUint());
            valueValue.PushBack(listValue, allocator);
        }
    }
    //添加新元素
    for (const uint32_t &v: value)
    {
        listValue.SetUint(v);
        valueValue.PushBack(listValue, allocator);
    }
    d->document.RemoveMember(key.c_str());
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::list<int64_t> &value, sh::PutListBehavior putListBehavior)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());

    rapidjson::Value valueValue(rapidjson::kArrayType);
    rapidjson::Document::AllocatorType &allocator = d->document.GetAllocator();

    rapidjson::Value listValue(rapidjson::kNumberType);
    //追加则先取出原来就存在的
    if (sh::PutListBehavior::Append == putListBehavior && d->document.HasMember(key.c_str()) && d->document[key.c_str()].IsArray())
    {
        for (auto &v: d->document[key.c_str()].GetArray())
        {
            if (!v.IsInt64())
            {
                //原先list中元素和目标元素不一致，则抛出异常
                throw sh::JsonMapTypeException("origin element type is not all int64_t");
            }
            listValue.SetInt64(v.GetInt64());
            valueValue.PushBack(listValue, allocator);
        }
    }
    //添加新元素
    for (const int64_t &v: value)
    {
        listValue.SetInt64(v);
        valueValue.PushBack(listValue, allocator);
    }
    d->document.RemoveMember(key.c_str());
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::list<uint64_t> &value, sh::PutListBehavior putListBehavior)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());

    rapidjson::Value valueValue(rapidjson::kArrayType);
    rapidjson::Document::AllocatorType &allocator = d->document.GetAllocator();

    rapidjson::Value listValue(rapidjson::kNumberType);
    //追加则先取出原来就存在的
    if (sh::PutListBehavior::Append == putListBehavior && d->document.HasMember(key.c_str()) && d->document[key.c_str()].IsArray())
    {
        for (auto &v: d->document[key.c_str()].GetArray())
        {
            if (!v.IsUint64())
            {
                //原先list中元素和目标元素不一致，则抛出异常
                throw sh::JsonMapTypeException("origin element type is not all uint64_t");
            }
            listValue.SetUint64(v.GetUint64());
            valueValue.PushBack(listValue, allocator);
        }
    }
    //添加新元素
    for (const uint64_t &v: value)
    {
        listValue.SetUint64(v);
        valueValue.PushBack(listValue, allocator);
    }
    d->document.RemoveMember(key.c_str());
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
sh::JsonMap sh::JsonMap::put(const std::string &key, const std::list<double> &value, sh::PutListBehavior putListBehavior)
{
    CHECK_PARSER
    rapidjson::Value keyValue;
    keyValue.SetString(key.c_str(), key.length(), d->document.GetAllocator());

    rapidjson::Value valueValue(rapidjson::kArrayType);
    rapidjson::Document::AllocatorType &allocator = d->document.GetAllocator();

    rapidjson::Value listValue(rapidjson::kNumberType);
    //追加则先取出原来就存在的
    if (sh::PutListBehavior::Append == putListBehavior && d->document.HasMember(key.c_str()) && d->document[key.c_str()].IsArray())
    {
        for (auto &v: d->document[key.c_str()].GetArray())
        {
            if (!v.IsNumber())
            {
                //原先list中元素和目标元素不一致，则抛出异常
                throw sh::JsonMapTypeException("origin element type is not all double");
            }
            listValue.SetDouble(v.GetDouble());
            valueValue.PushBack(listValue, allocator);
        }
    }
    //添加新元素
    for (const double &v: value)
    {
        listValue.SetDouble(v);
        valueValue.PushBack(listValue, allocator);
    }
    d->document.RemoveMember(key.c_str());
    d->document.AddMember(keyValue, valueValue, d->document.GetAllocator());
    return *this;
}
std::vector<std::string> sh::JsonMap::keys() const
{
    CHECK_PARSER
    if (!d->document.IsObject())
    {
        throw sh::JsonMapTypeException("data is not json object");
    }
    std::vector<std::string> keys;
    for (const auto &value: d->document.GetObject())
    {
        keys.emplace_back(value.name.GetString());
    }
    return keys;
}
uint32_t sh::JsonMap::size() const
{
    CHECK_PARSER
    if (!d->document.IsObject())
    {
        throw sh::JsonMapTypeException("data is not json object");
    }
    uint32_t size = 0;
    for (const auto &value: d->document.GetObject())
    {
        size++;
    }
    return size;
}
rapidjson::Document sh::JsonMap::getJsonValue() const
{
    return std::move(d->document);
}
